﻿namespace NTLib.WPF.Controls
{
    using Attributes;
    using Core.Extensions;
    using Core.Reflection;
    using Selectors;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;

    /// <summary>
    /// 
    /// </summary>
    /// <seealso cref="System.Windows.FrameworkElement" />
    [TemplatePart(Name = CONTAINER, Type = typeof(ItemsControl))]
    public class PropertyViewer : Control
    {
        #region Fields

        public const string CONTAINER = "PART_PropertyContainer";

        private ItemsControl _container;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes the <see cref="PropertyViewer"/> class.
        /// </summary>
        static PropertyViewer()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(PropertyViewer), new FrameworkPropertyMetadata(typeof(PropertyViewer)));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyViewer"/> class.
        /// </summary>
        public PropertyViewer()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        public object Source
        {
            get { return (object)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Source.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register("Source", typeof(object), typeof(PropertyViewer), new PropertyMetadata(null, OnPropertySourceUpdated));

        /// <summary>
        /// Gets the type of the source.
        /// </summary>
        /// <value>
        /// The type of the source.
        /// </value>
        public Type SourceType
        {
            get { return (Type)GetValue(SourceTypeProperty); }
            private set { SetValue(SourceTypeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SourceType.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SourceTypeProperty =
            DependencyProperty.Register("SourceType", typeof(Type), typeof(PropertyViewer), new PropertyMetadata(null));

        /// <summary>
        /// Gets or sets a value indicating whether [exposed only tag properties].
        /// </summary>
        /// <value>
        /// <c>true</c> if [exposed only tag properties]; otherwise, <c>false</c>.
        /// </value>
        public bool ExposedOnlyTagProperties
        {
            get { return (bool)GetValue(ExposedOnlyTagPropertiesProperty); }
            set { SetValue(ExposedOnlyTagPropertiesProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ExposedOnlyTagProperties.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ExposedOnlyTagPropertiesProperty =
            DependencyProperty.Register("ExposedOnlyTagProperties", typeof(bool), typeof(PropertyViewer), new PropertyMetadata(true));

        /// <summary>
        /// Gets or sets the item template selector.
        /// </summary>
        /// <value>
        /// The item template selector.
        /// </value>
        public DataTemplateSelector ItemTemplateSelector
        {
            get { return (DataTemplateSelector)GetValue(ItemTemplateSelectorProperty); }
            set { SetValue(ItemTemplateSelectorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ItemTemplateSelector.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ItemTemplateSelectorProperty =
            DependencyProperty.Register("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(PropertyViewer), new PropertyMetadata(null));

        #endregion

        #region Methods

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes call <see cref="M:System.Windows.FrameworkElement.ApplyTemplate" />.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (this._container != null)
                this._container.Items.Clear();

            this._container = base.Template.FindName(CONTAINER, this) as ItemsControl;
            this._container.ItemTemplateSelector = this.ItemTemplateSelector;
            this.Clean();
            this.LoadSource(Source);
        }

        /// <summary>
        /// Sources the updated.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private static void OnPropertySourceUpdated(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            PropertyViewer viewer = (PropertyViewer)sender;

            if (args.OldValue != null)
                viewer.Clean();

            if (args.NewValue != null)
                viewer.LoadSource(args.NewValue);
        }

        /// <summary>
        /// Loads the source.
        /// </summary>
        /// <param name="newValue">The new value.</param>
        private void LoadSource(object newValue)
        {
            if (newValue == null || this._container == null)
                return;

            this.SourceType = newValue.GetType();

            var props = (from el in this.SourceType.GetProperties()
                         where el.GetMethod != null &&
                         (!ExposedOnlyTagProperties || (el.HasAttr<PropertyExposedAttribute>() && el.GetCustomAttribute<PropertyExposedAttribute>().Exposed))
                         select el);

            this._container.Items.Clear();

            foreach (var prop in props)
                this._container.Items.Add(CreateItem(prop));
        }

        /// <summary>
        /// Creates the item.
        /// </summary>
        /// <param name="info">The information.</param>
        /// <returns></returns>
        protected virtual PropertyViewerItem CreateItem(PropertyInfo info)
        {
            return new PropertyViewerItem(info, this.Source);
        }

        /// <summary>
        /// Cleans this instance.
        /// </summary>
        private void Clean()
        {
            if (this._container != null)
            {
                object[] ctner = new object[this._container.Items.Count];
                this._container.Items.CopyTo(ctner, 0);
                this._container.Items.Clear();

                ctner.ToList().SafeClear();
            }
        }

        #endregion
    }
}
